﻿//-----------------------------------------------------------------------------
// <copyright file="ViewModel.cs" company="William E. Kempf">
//     Copyright (c) William E. Kempf.
// </copyright>
//-----------------------------------------------------------------------------

namespace Onyx.Windows
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Windows.Input;
    using Onyx.ComponentModel;
    using Onyx.Reflection;
    using System.Linq.Expressions;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// Base class for view model types.
    /// </summary>
    public abstract class ViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// The associated view.
        /// </summary>
        private readonly View view;

#if WPF
        /// <summary>
        /// The command bindings for the presenter.
        /// </summary>
        private readonly CommandBindingCollection commandBindings;
#endif

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModel"/> class.
        /// </summary>
        /// <param name="view">The view to associate with this instance.</param>
        protected ViewModel(View view)
        {
            this.view = view;

            IElementLifetime lifetime = this.view.GetService<IElementLifetime>();
            if (lifetime != null)
            {
#if WPF
                lifetime.Initialized += (s, e) => this.OnViewInitialized();
                lifetime.Unloaded += (s, e) => this.OnViewUnloaded();
#endif
                lifetime.Loaded += (s, e) => this.OnViewLoaded();
            }

#if WPF
            ICommandTarget commandTarget = this.view.GetService<ICommandTarget>();
            this.commandBindings =
                commandTarget == null ?
                new CommandBindingCollection() :
                commandTarget.CommandBindings;
#endif
        }

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        /// <summary>
        /// Gets the associated <see cref="View"/> instance.
        /// </summary>
        /// <value>The associated <see cref="View"/>.</value>
        public View View
        {
            get { return this.view; }
        }

#if WPF
        /// <summary>
        /// Gets the command bindings.
        /// </summary>
        /// <value>The command bindings.</value>
        protected CommandBindingCollection CommandBindings
        {
            get { return this.commandBindings; }
        }
#endif

#if WPF
        /// <summary>
        /// Called when <see cref="ViewModel.View"/> is initialized.
        /// </summary>
        protected virtual void OnViewInitialized()
        {
        }

        /// <summary>
        /// Called when <see cref="ViewModel.View"/> is unloaded.
        /// </summary>
        protected virtual void OnViewUnloaded()
        {
        }
#endif

        /// <summary>
        /// Called when <see cref="ViewModel.View"/> is loaded.
        /// </summary>
        protected virtual void OnViewLoaded()
        {
        }

        /// <summary>
        /// Called when a property changes, using static reflection to determine the property.
        /// </summary>
        /// <typeparam name="T">The type of the property.</typeparam>
        /// <param name="expression">Expression that accesses the property that changed.</param>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        protected void OnPropertyChanged<T>(Expression<Func<T>> expression)
        {
            this.OnPropertyChanged(Reflect.GetProperty(expression).Name);
        }

        /// <summary>
        /// Called when the property <paramref name="propertyName"/> has changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Verifies the name of the property.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        [Conditional("DEBUG")]
        private void VerifyPropertyName(string propertyName)
        {
            if (!string.IsNullOrEmpty(propertyName) && this.GetType().GetProperty(propertyName) == null)
            {
                throw new ArgumentException("Not a proprety.", Reflect.GetField(() => propertyName).Name);
            }
        }
    }
}
